package mail

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"mime"
	"net"
	"net/mail"
	"net/smtp"
	"pay/pkg/ecode"
	"pay/pkg/env"
	"pay/pkg/log"
	"pay/pkg/request_key"
	"pay/pkg/util"
	"time"
)

// Config is struct of sender config
type Config struct {
	Addr     string `mapstructure:"addr"`
	From     string `mapstructure:"from"`
	FromName string `mapstructure:"from_name"`
	Password string `mapstructure:"password"`
	StartSSL bool   `mapstructure:"start_ssl"`
	Maintain string `mapstructure:"maintain"`
}

func (c *Config) Init() (sender *SMTPSender, err error) {
	var host string
	if host, _, err = net.SplitHostPort(c.Addr); err != nil {
		err = fmt.Errorf("邮件服务地址配置解析失败")
		return
	}
	sender = &SMTPSender{
		addr: c.Addr,
		from: mail.Address{Name: c.FromName, Address: c.From},
		auth: smtp.PlainAuth("", c.From, c.Password, host),
	}
	if c.StartSSL {
		sender.cfg = &tls.Config{
			InsecureSkipVerify: true,
			ServerName:         host,
		}
	}
	return
}

// Message is struct used to send msg
type Message struct {
	Subject   string
	Content   io.Reader         // support html content
	To        []string          // to address string
	Extension map[string]string // message extension
}

// SMTPSender used to send mail with smtp protocol
type SMTPSender struct {
	addr string
	from mail.Address
	auth smtp.Auth
	cfg  *tls.Config
}

func (s *SMTPSender) FormatMessage(subject, content string, toAddresses []string, envType env.Type) *Message {
	switch envType {
	case env.Production:
		subject = fmt.Sprintf("%s - %s", subject, time.Now().Format(util.DefaultLayout))
	default:
		subject = fmt.Sprintf("%s(%s) - %s", subject,
			envType.GetEnvName(), time.Now().Format(util.DefaultLayout))
	}
	return &Message{
		Subject: fmt.Sprintf("%s - %s", subject, time.Now().Format(util.DefaultLayout)),
		Content: bytes.NewBufferString(fmt.Sprintf("<html>\n  <head></head>\n  <body>\n    %s\n  </body>\n</html>",
			content)),
		To: toAddresses,
	}
}

func (s *SMTPSender) MakeHTMLTables(title string, headers []string, dataMap []map[string]string) (content string) {
	if len(dataMap) == 0 || len(headers) == 0 {
		return
	}
	content = fmt.Sprintf("%s: <br>", title)
	content += "<table border =\"1\" style=\"width:100%\"><tr>"
	for _, header := range headers {
		content += fmt.Sprintf("<th>%s</th>", header)
	}
	content += "</tr>"
	for _, lineMap := range dataMap {
		content += "<tr>"
		for _, header := range headers {
			content += fmt.Sprintf("<td>%s</td>", lineMap[header])
		}
		content += "</tr>"
	}
	content += "</table><br>"
	return content
}

// AsyncSend async send mail
func (s *SMTPSender) AsyncSend(ctx context.Context, message *Message, env env.Type) {
	go func() {
		newCtx := request_key.NewContextWithReqID(ctx)
		var err error
		defer ecode.AsyncFailOver(newCtx, "async send mails", env, &err)
		if err = s.send(message); err != nil {
			return
		}
		log.FromContext(newCtx).Info(
			"send 1 mail")
	}()
}

func (s *SMTPSender) send(msg *Message) error {
	for _, addr := range msg.To {
		header := s.getHeader(msg.Subject)
		header["To"] = addr
		if msg.Extension != nil {
			for k, v := range msg.Extension {
				header[k] = v
			}
		}
		data := s.getData(header, msg.Content)
		conn, err := tls.Dial("tcp", s.addr, s.cfg)
		if err != nil {
			return err
		}
		host, _, err := net.SplitHostPort(s.addr)
		if err != nil {
			return err
		}
		client, err := smtp.NewClient(conn, host)

		if err != nil {
			return err
		}
		if err = client.Auth(s.auth); err != nil {
			return err
		}

		if err = client.Mail(s.from.Address); err != nil {
			return err
		}

		if err = client.Rcpt(addr); err != nil {
			return err
		}

		w, err := client.Data()
		if err != nil {
			return err
		}
		_, err = w.Write(data)
		if err != nil {
			return err
		}
		err = w.Close()
		if err != nil {
			return err
		}
		err = client.Quit()
		if err != nil {
			return err
		}
	}
	return nil
}

func (s *SMTPSender) getHeader(subject string) map[string]string {
	header := make(map[string]string)
	header["From"] = s.from.String()
	header["Subject"] = mime.QEncoding.Encode("utf-8", subject)
	header["Mime-Version"] = "1.0"
	header["Content-Type"] = "text/html;charset=utf-8"
	header["Content-Transfer-Encoding"] = "Quoted-Printable"
	return header
}

func (s *SMTPSender) getData(header map[string]string, body io.Reader) []byte {
	buf := new(bytes.Buffer)
	for k, v := range header {
		_, _ = fmt.Fprintf(buf, "%s: %s\r\n", k, v)
	}
	_, _ = fmt.Fprintf(buf, "\r\n")
	_, _ = io.Copy(buf, body)
	return buf.Bytes()
}
